/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "ScriptOperationParser.h"
#include "ScriptSemantics_abstract.h"
#include "simodo/interpret/AnalyzeException.h"
#include "simodo/inout/format/fmt.h"

namespace simodo::interpret
{
    InterpretState OperationParser::parseNone(ScriptSemantics_abstract &host, const ast::Node & )
    {
        // node(None, )

        return host.executeNone();
    }

    InterpretState OperationParser::parsePushConstant(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(PushConstant, constant_value)
        //    |
        //    ---- node(, unit) * 0..n

        return host.executePushConstant(op);
    }

    InterpretState OperationParser::parsePushVariable(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(PushVariable, variable_name)

        return host.executePushVariable(op.token());
    }

    InterpretState OperationParser::parseObjectElement(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(ObjectElement, variable_name)

        return host.executeObjectElement(op);
    }

    InterpretState OperationParser::parseFunctionCall(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(FunctionCall, parenthesis)
        //    |
        //    ---- node() * 0..n (список выражений)

        return host.executeFunctionCall(op);
    }

    InterpretState OperationParser::parseProcedureCheck(ScriptSemantics_abstract &host, const ast::Node & )
    {
        // node(ProcedureCheck, )

        // Предполагается, что оператор ProcedureCheck выполняется сразу после вызова функции/процедуры

        return host.executeProcedureCheck();
    }

    InterpretState OperationParser::parsePrint(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(Print, needDetailedReport)

        return host.executePrint(op.token().lexeme() == u"all");
    }

    InterpretState OperationParser::parseBlock(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(Block, isBeginningOfBlock)
        //    |
        //    ---- node() * 0.. (список операторов)

        return host.executeBlock(!op.branches().empty(), op);
    }

    InterpretState OperationParser::parsePop(ScriptSemantics_abstract &host, const ast::Node & )
    {
        // node(Pop, )

        return host.executePop();
    }

    InterpretState OperationParser::parseReference(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(Reference, )

        return host.executeReference(op);
    }

    InterpretState OperationParser::parseArrayElement(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(ArrayElement, dot)
        //    |
        //    ---- node(None, name) * 1.. (список выражений, интерпретируемых, как набор индексов)

        return host.executeArrayElement(op);
    }

    InterpretState OperationParser::parseRecordStructure(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(RecordStructure, dot)
        //    |
        //    ---- node(None, name) * 0.. (список именованных выражений)

        return host.executeObjectStructure(op);
    }

    InterpretState OperationParser::parseArrayStructure(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(ArrayStructure, dot)
        //    |
        //    ---- node(None, name) * 0.. (список выражений, интерпретируемых, как значения одномерного массива)

        return host.executeArrayStructure(op);
    }

    InterpretState OperationParser::parseImport(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(Import, path)
        //    |
        //    ---- node(None, ) * 0..1

        return host.executeImport(op.token(), op);
    }

    InterpretState OperationParser::parseContractDefinition(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(ContractDefinition, contract_name)

        return host.executeContract(op);
    }

    InterpretState OperationParser::parseAnnouncement(ScriptSemantics_abstract &host, const ast::Node & )
    {
        // node(Announcement, )

        return host.executeAnnouncement();
    }

    InterpretState OperationParser::parseDeclaration(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(Declaration, ID)

        return host.executeDeclaration(op);
    }

    InterpretState OperationParser::parseDeclarationCompletion(ScriptSemantics_abstract &host, const ast::Node & )
    {
        // node(DeclarationCompletion, )

        return host.executeDeclarationCompletion();
    }

    InterpretState OperationParser::parseAssignment(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(<assignment>, )
        //    |
        //    ---- node(..., ...) * 1.. (выражение)
        //
        // <assignment>:
        //      Initialize
        //      Assignment
        //      AssignAddition
        //      AssignSubtraction
        //      AssignMultiplication
        //      AssignDivision
        //      AssignModulo

        return host.executeAssignment(op);
    }

    InterpretState OperationParser::parseGroupInitialize(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(GroupInitialize, )
        //    |
        //    ---- node(GroupDeclaration, "[") * 1
        //            |
        //            ---- node(Declaration, ID) * 0.. (список ID)

        std::vector<const inout::Token *>  ids;
        ast::Node                          rvalue_ops;

        for(const ast::Node & n : op.branches())
            if (static_cast<ScriptOperationCode>(n.operation()) == ScriptOperationCode::GroupDeclaration) {
                for(const ast::Node & id : n.branches())
                    ids.push_back(&id.token());
            }
            // else if (static_cast<ScriptOperationCode>(n.operation()) == ScriptOperationCode::GroupDefinition)
            //     p_rvalue_ops = & n;

        if (ids.empty())
            throw bormental::DrBormental("OperationParser::parseGroupInitialize", 
                                inout::fmt("Incorrect group initialization structure"));

        return host.executeGroupInitialize(op, ids, rvalue_ops);
    }

    InterpretState OperationParser::parsePostAssignment(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        return host.executePostAssignment(op);
    }

    InterpretState OperationParser::parseUnary(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(<unary>, )
        // <unary>:
        //      Plus
        //      Minus
        //      Not

        return host.executeUnary(op);
    }

    // State OperationParser::parseIterations(ScriptSemantics_abstract &host, const ast::Node & op)
    // {
    //     // node(<iteration>, )
    //     // <iteration>:
    //     //      PreAdd
    //     //      PreSub
    //     //      PosAdd
    //     //      PosSub

    //     return host.executeIterations(op);
    // }

    InterpretState OperationParser::parseLogical(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(<logical>, )
        // <logical>:
        //      Or
        //      And

        return host.executeLogical(op);
    }

    InterpretState OperationParser::parseCompare(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(<compare>, )
        // <compare>:
        //      Equal
        //      NotEqual
        //      Less
        //      LessOrEqual
        //      More
        //      MoreOrEqual

        return host.executeCompare(op);
    }

    InterpretState OperationParser::parseArithmetic(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(<arithmetic>, )
        // <arithmetic>:
        //      Addition
        //      Subtraction
        //      Multiplication
        //      Division
        //      Modulo
        //      Power

        return host.executeArithmetic(op);
    }

    InterpretState OperationParser::parseConditional(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(<conditional>, )
        // <conditional>:
        //      Ternary
        //      If

        if (op.branches().empty())
            throw bormental::DrBormental("OperationParser::parseConditional", 
                                inout::fmt("Incorrect group conditional structure"));

        return host.executeConditional(static_cast<ScriptOperationCode>(op.operation()),
                                    op.branches().front(), 
                                    op.branches().front().operation() == op.branches().back().operation() ? nullptr : &op.branches().back());
    }

    InterpretState OperationParser::parseFunctionDefinition(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        // node(Function, fn) #31 fn
        // |
        // +- Function_Closure #3101 [ // nullable
        // |  |
        // |  +- Function_Closure_Id #3102 <ID>
        // |  +- ...
        // |
        // +- Function_Params #3103 ( // nullable
        // |  |
        // |  +- <code> // код из объявления переменных
        // |  +- ...
        // |
        // +- Function_Return_Type #3106 -> // nullable
        // |  |
        // |  +- <code> // код из объявления переменных
        // |  +- ...
        // |
        // +- Function_Body #3107 { // not nullable
        //    |
        //    +- <code>
        //    +- ...
        //    +- #0 }

        if (op.branches().empty())
            throw bormental::DrBormental("OperationParser::parseFunctionDefinition", 
                                inout::fmt("Incorrect function declaration structure"));

        const ast::Node *   pop_closure = nullptr;
        const ast::Node *   pop_params  = nullptr;
        const ast::Node *   pop_return_type= nullptr;
        const ast::Node *   pop_body    = nullptr;

        for(const ast::Node & o : op.branches())
            switch (static_cast<ScriptOperationCode>(o.operation()))
            {
            case ScriptOperationCode::Function_Closure:
                if (pop_closure)
                    throw bormental::DrBormental("OperationParser::parseFunctionDefinition", 
                                        inout::fmt("Incorrect function declaration structure"));
                pop_closure = &o;
                break;
            case ScriptOperationCode::Function_Params:
                if (pop_params)
                    throw bormental::DrBormental("OperationParser::parseFunctionDefinition", 
                                        inout::fmt("Incorrect function declaration structure"));
                pop_params = &o;
                break;
            case ScriptOperationCode::Function_Return_Type:
                if (pop_return_type)
                    throw bormental::DrBormental("OperationParser::parseFunctionDefinition", 
                                        inout::fmt("Incorrect function declaration structure"));
                pop_return_type = &o;
                break;
            case ScriptOperationCode::Function_Body:
                if (pop_body)
                    throw bormental::DrBormental("OperationParser::parseFunctionDefinition", 
                                        inout::fmt("Incorrect function declaration structure"));
                pop_body = &o;
                break;
            default:
                throw bormental::DrBormental("OperationParser::parseFunctionDefinition", 
                                        inout::fmt("Incorrect function declaration structure"));
            }

        if (pop_body == nullptr)
            throw bormental::DrBormental("OperationParser::parseFunctionDefinition", 
                                inout::fmt("Incorrect function declaration structure"));

        return host.executeFunctionDefinition(pop_closure, pop_params, pop_return_type, *pop_body);
    }

    InterpretState OperationParser::parseFunctionDefinitionEnd(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        return host.executeFunctionDefinitionEnd(op);
    }

    InterpretState OperationParser::parseFunctionTethered(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        return host.executeFunctionTethered(op);
    }

    InterpretState OperationParser::parseReturn(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        return host.executeReturn(op);
    }

    InterpretState OperationParser::parseFor(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        const ast::Node * pop_variable = nullptr;
        const ast::Node * pop_source   = nullptr;
        const ast::Node * pop_body     = nullptr;
        bool              error        = false;

        for(const ast::Node & n : op.branches())
            switch(ScriptOperationCode(n.operation()))
            {
            case ScriptOperationCode::For_Variable:
                if (pop_variable)
                    error = true;
                else
                    pop_variable = &n;
                break;
            case ScriptOperationCode::For_Source:
                if (pop_source)
                    error = true;
                else
                    pop_source = &n;
                break;
            case ScriptOperationCode::Cycle_Body:
                if (pop_body)
                    error = true;
                else
                    pop_body = &n;
                break;
            default:
                error = true;
                break;
            }

        if (error || pop_variable == nullptr || pop_source == nullptr || pop_body == nullptr)
            throw bormental::DrBormental("OperationParser::parseFor", 
                                inout::fmt("Incorrect operator 'for' structure"));

        return host.executeFor(op.token(), pop_variable, pop_source, pop_body);
    }

    InterpretState OperationParser::parseWhile(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        const ast::Node * pop_expression = nullptr;
        const ast::Node * pop_body       = nullptr;
        bool              error          = false;

        for(const ast::Node & n : op.branches())
            switch(ScriptOperationCode(n.operation()))
            {
            case ScriptOperationCode::While_Condition:
                if (pop_expression)
                    error = true;
                else
                    pop_expression = &n;
                break;
            case ScriptOperationCode::Cycle_Body:
                if (pop_body)
                    error = true;
                else
                    pop_body = &n;
                break;
            default:
                error = true;
                break;
            }

        if (error || pop_expression == nullptr || pop_body == nullptr)
            throw bormental::DrBormental("OperationParser::parseWhile", 
                                inout::fmt("Incorrect operator 'while' structure"));

        return host.executeWhile(op.token(), pop_expression, pop_body);
    }

    InterpretState OperationParser::parseDoWhile(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        const ast::Node * pop_expression = nullptr;
        const ast::Node * pop_body       = nullptr;
        bool              error          = false;

        for(const ast::Node & n : op.branches())
            switch(ScriptOperationCode(n.operation()))
            {
            case ScriptOperationCode::While_Condition:
                if (pop_expression)
                    error = true;
                else
                    pop_expression = &n;
                break;
            case ScriptOperationCode::Cycle_Body:
                if (pop_body)
                    error = true;
                else
                    pop_body = &n;
                break;
            default:
                error = true;
                break;
            }

        if (error || pop_expression == nullptr || pop_body == nullptr)
            throw bormental::DrBormental("OperationParser::parseDoWhile", 
                                inout::fmt("Incorrect operator 'do-while' structure"));

        return host.executeDoWhile(op.token(), pop_body, pop_expression);
    }

    InterpretState OperationParser::parseBreak(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        return host.executeBreak(op);
    }

    InterpretState OperationParser::parseContinue(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        return host.executeContinue(op);
    }

    InterpretState OperationParser::parseApply(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        std::vector<inout::Token> names;

        if (op.branches().empty())
            throw bormental::DrBormental("OperationParser::parseApply", 
                                inout::fmt("Incorrect operator 'apply' structure"));

        for(const ast::Node & n : op.branches())
            names.push_back(n.token());

        return host.executeApply(op, names);
    }

    // InterpretState OperationParser::parseAutoDefine(ScriptSemantics_abstract &host, const ast::Node & op)
    // {
    //     return host.executeAutoDefine(op);
    // }

    InterpretState OperationParser::parseFiberOperations(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        switch (ScriptOperationCode(op.operation()))
        {
        case ScriptOperationCode::FiberMake:
            return host.executeFiberMake(op);
        
        case ScriptOperationCode::FiberWait:
            return host.executeFiberWait(op);

        case ScriptOperationCode::FiberPull:
        case ScriptOperationCode::FiberPush:
            return host.executeFiberPushPull(op);

        case ScriptOperationCode::FiberFlow:
            return host.executeFiberFlow(op);

        default:
            break;
        }

        throw bormental::DrBormental("OperationParser::parseFiberOperations", 
                            inout::fmt("Incorrect inter operation structure"));
    }

    InterpretState OperationParser::parseCheckState(ScriptSemantics_abstract &host, const ast::Node & op)
    {
        return host.executeCheckState(op);
    }

}